WMIC If / Else Statement for Software Version - powershell

I'm trying to use PowerShell to move install files, uninstall any previous version of the software, remove the install directories, and execute the BAT file.
My domain has finally updated and allowed WinRM to run on our machines, making patching much easier to facilitate remotely. I'm working my first script to do this involving updating Java. What I want to do is use PowerShell Studio to deploy a script, this script will kill all the tasks Java is attached to, use wmic to query the installed Java version and call for uninstall, and then Start-Process a BAT file which will do the install, and then clean itself up. What's happening is when I run into a machine with NO Java on it, I get "No Instance(s) Available".
Googling and looking around here, I can't seem to get my If / Else statement right and was looking for some help.
taskkill /F /IM iexplorer.exe
taskkill /F /IM chrome.exe
taskkill /F /IM outlook.exe
wmic product where "name like 'Java%%'" call uninstall /nointeractive
Start-Process -FilePath 'C:\Suppot\Java\java.bat' -Verb runas -Wait
RD /S /Q "C:\support\java"
What I would like to happen is to watch the machine update and install Java quietly in the background, refreshing Control Panel to verify in testing, that it works.
What happened is there was an error in the code, and the uninstall worked and it failed after that. On the next run, it now fails when it can't find a version of Java to remove.

The way your script is written is not very PoSh. Essentially you're just running batch code in PowerShell.
For enumerating/killing processes use Get-Process:
Get-Process -Name 'chrome', 'iexplore', 'outlook' | ForEach-Object { $_.Kill() }
For querying WMI you'd use Get-WmiObject or Get-CimInstance (the latter is essentially a modernized version of the former) unless you're really pressed for performance. Then, and only then, you'd resort to wmic.
However, for your particular task one wouldn't use WMI in the first place, because querying the Win32_Product class is considered harmful. Look up the uninstall string in the registry instead, split the string, and run it via Start-Process. Add the argument /qn to the parameter string for an unattended removal.
$path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
Get-ChildItem $path | ForEach-Object {
Get-ItemProperty -Path $_.PSPath | Where-Object {
$_.DisplayName -like '*java*'
} | ForEach-Object {
$cmd, $params = $_.UninstallString -split ' ', 2
Start-Process $cmd -ArgumentList "${params} /qn" -Wait
}
}
Files and folders can be removed with Remove-Item:
Remove-Item 'C:\support\java' -Recurse -Force

Related

msiexec Powershell silent install

I am searching for a Powershell Script which allows me to silent install a msi file.
We have over 25000 PCs so i have to do that with a script.
Unfortunately at the moment a window popping up (Windows Installer) after the execution which shows the parameter of a msi file. Nothing more, no other "error messages" are popping up.
The first thing the Script should do is to check if the PC is a Desktop or Mobile Device.
If its a Desktop device he should write in a file "Desktop Configuration was used".
In the same time the msi installer should start with some parameter.
If its a Laptop the procedure should be nearly the same.
After the installation is successful the user should be signed out.
I need this script to implement 2FA in our company.
The code at the moment looks like this:
IF ( ((Get-ComputerInfo | select -expand CsPCSystemType) -LIKE "Desktop") )
{
Write-Output "Desktop Configuration was used." >> \\XXX\XXX\XXX\XXX\Log\$env:Computername.txt
msiexec.exe /i "%~dp0setup.msi" /passive /norestart /L*v "%~dp0setup.log"
}
ELSE {
Write-Output "Laptop Configuration was used." >> \\XXX.XXX.XX\X\XX\XXX\XXXX\$env:Computername.txt
msiexec.exe /i "%~dp0setup.msi" /passive /norestart /L*v "%~dp0setup.log"
}
Write-Output "Lock Configuration was used." >> \\XXX\XXX\XXX\XXX\Log\$env:Computername.txt
rundll32.exe user32.dll,LockWorkStation
Any help is really appreciated.
The token %~dp0 (which resolves to the directory where the current script resides in) only works in batch scripts.
In PowerShell, replace $~dp0 by $PSScriptRoot. That should solve the problem of msiexec showing up with the available command-line options.
Another problem of your script is that msiexec runs asynchronously, when called directly as a command. So your script will be finished while the installation is still running, which is propably not what you want. This is caused by msiexec.exe being a GUI application. To run GUI applications synchronously, use Start-Process -Wait.
$Arguments = "/i", "`"$PSScriptRoot\setup.msi`"", "/passive", "/norestart", "/L*v", "`"$PSScriptRoot\setup.log`""
$msiProcess = Start-Process msiexec.exe -Wait -ArgumentList $Arguments -PassThru
# Check if installation was successful
# 0 = ERROR_SUCCESS, 3010 = ERROR_SUCCESS_REBOOT_REQUIRED
if( $msiProcess.ExitCode -in 0, 3010 ) {
Write-Host "Installation succeeded with exit code $($msiProcess.ExitCode)"
}
Try passing silent switches (/qn! or /qb!) instead of /passive flag.

Application doesn't get triggered from Powershell script, when invoking inside Do...Until

I am trying to build custom Windows System Utility script which offers some tasks with relevant keypress choices.
For cleanup task, I am trying to invoke CCleaner64.exe from this script, with it's correct switches as mentioned here. And the script I built so far is below:
$ScriptDir = Split-Path $MyInvocation.MyCommand.Path
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
$CommandLine = "-File `"" + $MyInvocation.MyCommand.Path + "`" " + $MyInvocation.UnboundArguments
Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList $CommandLine
Exit
}
}
Set-Location $ScriptDir; Echo 'Current Directory: ' + (Get-Location | Out-String)
function SysUtilMenu {
param (
[string]$Title = 'Windows System Utility'
)
Clear-Host
Write-Host "============ $Title ==========="
Write-Host "1: Do task 1 here."
Write-Host "2: Cache/Temp Files Cleanup."
Write-Host "Q: Exit this Application."
}
Do
{
SysUtilMenu
$selection = Read-Host "Press key to run given task..."
switch ($selection)
{
'1' {
## Do task 1 here...
} '2' {
$CclnrApp64 = "$Env:ProgramFiles\CCleaner\CCleaner64.exe"
Start-Process $CclnrApp64 -ArgumentList '/Clean'
Start-Process $CclnrApp64 -ArgumentList '/Registry'
}
}
}
Until($selection -eq 'q')
But when I press '2', it doesn't invoke CCleaner64.exe in the taskbar, which I checked.
I don't get, why the same Start-Process line doesn't work in that script, but if I open the Powershell terminal separately and run below commands one-by-one, it works perfectly ?
$CclnrApp64 = "$Env:ProgramFiles\CCleaner\CCleaner64.exe"
Start-Process $CclnrApp64 -ArgumentList '/Clean'
Is it due to Script's Self-Elevation, I have taken care of setting the location of the script instead of C:\Windows\System32.
Suggestion with detailed explanation is greatly appreciated...
From the link you have added, the documentation under Command-line parameters for CCleaner operation pane focus, it says the switch should be /CLEANER, not /Clean, and since your code also shows the switch /Registry, I thought this is what you were after (to open the app on a particular tab page).
My initial thoughts were:
it is possible you need to add the -Wait switch so PowerShell will ony start the second command after the first one has completed.
so the ful command would be Start-Process -FilePath "$CclnrApp64" -ArgumentList '/Cleaner' -Wait
to try and use the call operator & instead of Start-Process like & "$CclnrApp64" "/CLEANER"
Both above have the paths in variable $CclnrApp64 in between quotes because $env:programfiles will usually expand to C:\Program Files which has a space in the path.
Under Command-line parameters for CCleaner Business and Technician Edition, there is also a switch called /Clean
If you have that version, the switch should clean up using whatever rules are rules defined in ccleaner.ini and optionally puts the results in log_filename.txt
However, on that same CCleaner page, there is also a listing of other parameters, especially for use in a commandline and as you have experimented using the /AUTO switch, it appears this is what you were after:
CCleaner runs silently and automatically, using the current set of saved options to clean the PC. CCleaner then exits.
A note about the /AUTO switch though:
When you run CCleaner.exe using the /AUTO parameter, CCleaner does not run the Registry cleaner. You cannot currently run the Registry cleaner through a command-line parameter
All this means there are several switches you can use with CCleaner, but they all serve a different purpose.
/CLEANER, /REGISTRY, /TOOLS and /OPTIONS are for opening the application at a certain pane
/AUTO (with optional /SHUTDOWN), /EXPORT and /DELETE (with optional /METHOD) are to have the application perform cleaning/delete actions
and for the Business and Technician Edition there is also
/analyze, /clean and /update

PowerShell function not running as expected

I have a curious case that I cannot fathom the reason for...
Please know I am a novice to PowerShell.
I am working on a PowerShell menu system to help automate building out new computers in my environment. I have a PS1 file that holds the script for an app install. When I use the script to reference this I am able to run it and have no issue. However, when I try inserting this into a function and referencing it does not.
This works:
4 # Microsoft Office 32-bit
{
Write-Host "`nMicrosoft Office 32-bit..." -ForegroundColor Yellow
# {installMS32Bit}
Invoke-Expression "cmd /c start powershell -NoExit -File '\\**SERVERPATH**\menuItems\ms_office\32-bit\install.ps1'"
Start-Sleep -seconds 2
}
This does not:
function installMS32Bit(){
Invoke-Expression "cmd /c start powershell -NoExit -File '\\**SERVERPATH**\menuItems\ms_office\32-bit\install.ps1'"
}
}
4 # Microsoft Office 32-bit
{
Write-Host "`nMicrosoft Office 32-bit..." -ForegroundColor Yellow
{installMS32Bit}
Start-Sleep -seconds 2}
install.ps1 file:
# Copy MS Office uninstall and setup to local then run and install 32-bit Office
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\setup.exe' -Destination 'C:\temp\' -Force
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\uninstall.xml' -Destination 'C:\temp\' -Force
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\32-bit\Setup.exe' -Destination 'C:\temp' -Force
Invoke-Expression ("cmd /c 'C:\temp\setup.exe' /configure 'C:\temp\uninstall.xml'")
Start-Process -FilePath 'C:\temp\Setup.exe'
Secondary question and a little explanation for Invoke-Expression...
I like to see progress and like to have secondary windows open to monitor the new process being run. I was unable to find a solution with a persistent window that worked for me to do this without Invoke-Expression.
If there is a better way to do this in PowerShell I am all ears!
{installMS32Bit}
As Mathias points out in a comment on the question, this statement doesn't call your function, it wraps it in a script block ({ ... })[1], which is a piece of reusable code (like a function pointer, loosely speaking), for later execution via &, the call (execute) operator.
To call your function, just use its name (by itself here, given that there are no arguments to pass): installMS32Bit
Invoke-Expression should generally be avoided; definitely don't use it to invoke an external program, as in your attempts.
Additionally, there's generally no need to call an external program via cmd.exe (cmd /c ...), just invoke it directly.
For instance, replace the last Invoke-Epression call from your question with:
# If the EXE path weren't quoted, you wouldn't need the &
& 'C:\temp\setup.exe' /configure 'C:\temp\uninstall.xml'
I like to see progress and like to have secondary windows open to monitor the new process being run. I was unable to find a solution with a persistent window that worked for me to do this without Invoke-Expression.
(On Windows), Start-Process by default executes a console application in a new window (unless you specify -NoNewWindow), asynchronously (unless you specify -Wait).
You cannot pass a .ps1 script directly to Start-Process (it will be treated like a document to open rather than an executable to call), but you can pass it to PowerShell's CLI via the -File parameter:
Start-Process powershell.exe '-File install.ps1'
The above is short for:
Start-Process -FilePath powershell.exe -ArgumentList '-File install.ps1'
That is, PowerShell will execute the following in a new window:
powershell.exe -File install.ps1
[1] Since you're not assigning the script block being created to a variable, it is implicitly output (printed to the display, in the absence of a redirection); a script block stringifies by its literal contents, excluding the enclosing { and }, so string installMS32Bit will print to the display.

How to bypass security warning when running EXE from network location?

I am trying to write a complex unattended install script that installs from a network directory. I'm running PS in administrator mode with bypass security.
When I run:
Start-Process "\\192.168.5.7\MSChart.exe" -ArgumentList "/q" -Wait
I get:
How can I bypass this without adding the network location as a trusted server? Ideally simply using PowerShell. I've tried Unblock-File, no luck.
The network share is not trusted by your computer, hence it warns you. You would have to add the share to the trusted zone in the systems internet settings, and allow "launching programs and unsafe files".
You cannot bypass it, but
add the required configuration to the registry
or copy the files locally and run it from there
using PowerShell
You can bypass the warning by adding -NoNewWindow as in Start-Process "\\192.168.5.7\MSChart.exe" -ArgumentList "/q" -Wait -NoNewWindow.
You should however leverage DNS for your path (e.g. \\share.domain.com\file.exe) and ensure the URI (share.domain.com) is in your system 'Trusted Sites' or 'Intranet Sites' list or you may still be blocked. Copying the file to the local system first may also fix the problem.
Reference: https://social.technet.microsoft.com/Forums/en-US/92eab96d-fe1a-4119-a5bc-f171d517466a/getting-open-file-security-warning-using-startprocess?forum=winserverpowershell
Maybe you want to Unblock-File and accept all of the risks that come with that and then try to execute it?
I don't recommend anyone EVER run a script like this:
function Unblock-Dir()
{
gci -Directory | % {
push-location $_ ;
gci | % {
Write-Host "Unblocking $_";
Unblock-File $_
}
Unblock-Dir ;
Pop-Location
}
Unblock-File -path .\*
}
It's just too dangerous.

Powershell uninstall program with msiexec

I've run into a problem getting msiexec to remove java with Powershell. I've output my resultant command to the screen and pasted it into a batch file and it runs great. But when it's executed via Powershell it fails saying the "package cannot be found". Can anyone spot what I might be doing wrong? I've looked up and down google and tried a few different ways of executing the command w/o success and with the same result.
cls
$java = Get-WmiObject -Class win32_product | where { $_.Name -like "*Java*"}
$msiexec = "c:\windows\system32\msiexec.exe";
#$msiexecargs = '/x:"$app.LocalPackage" /qr'
$msiexecargs = '/uninstall "$app.IdentifyingNumber" /qr /norestart'
if ($java -ne $null)
{
foreach ($app in $java)
{
write-host $app.LocalPackage
write-host $app.IdentifyingNumber
#&cmd /c "msiexec /uninstall $app.IdentifyingNumber /passive"
#Start-Process -FilePath $msiexec -Arg $msiexecargs -Wait -Passthru
[Diagnostics.Process]::Start($msiexec, $msiexecargs);
}
}
else { Write-Host "nothing to see here..." }
Write-Host "check end"
The goal is to use the Windows 7 logon script to remove all versions of Java on end-user systems and then install the latest. I prefer to make it all Powershell, but if I can't get this working I'll just use a batch file hard coded with the uninstall GUID's
The write-host statements are all for the purpose of debugging, I'm just interested in the execution of msiexec in some variation of this format: msiexec /x {GUID} /passive /norestart
The error I get is:
"This installation package could not be opened. Verify that the package exists and that you can access it, or contact the application vendor to verify that this is a valid Windows Installer package."
I know it works on its own, just not in this script...so I'm thinking it's a syntax thing.
If you have any questions let me know.
First you have to know the difference between this:
"$app.IdentifyingNumber"
and this
"$($app.IdentifyingNumber)"
So I think you wanted to use the latter (the code is a little bit confusing because of the commented lines):
&cmd /c "msiexec /uninstall $($app.IdentifyingNumber) /passive"