(Get-WmiObject -Class Win32_Product -ComputerName $PCNumber -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "$softwareName" }).Uninstall() | Out-Null
I have following code which works perfectly. The only problem is that I wont to know if the software has been removed or not.This doesn't tells me but the code below does.
This way works for me.
$software = Get-WmiObject -Class Win32_Product -ComputerName $PCNumber -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "$softwareName" }
$soft = $software.Uninstall();
$n = $software.ReturnValue;
if ( $n -eq 0 ){
SOFTWARE HAS BEEN REMOVED.
}
my question is that how do i tell if the software has been removed or not.
using this code.
(Get-WmiObject -Class Win32_Product -ComputerName $PCNumber -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "$softwareName" }).Uninstall() | Out-Null
You have to check the ReturnValue property. When you pipe to Out-Null you are suppressing the output of the operation and there's no way to tell what happened, unless you issue a second call to find if it returns the software in question.
I recommend using the Filter parameter (instead of using Where-Object) to query the software on the server. To be safe you should also pipe the results to the Foreach-Object cmdlet, you never know how many software objects you get back due to the match operation (and you call the Uninstall method as if the result is one object only):
Get-WmiObject -Class Win32_Product -ComputerName $PCNumber -Filter "Name LIKE '%$softwareName%'" | Foreach-Object {
Write-Host "Uninstalling: $($_.Name)"
$rv = $_.Uninstall().ReturnValue
if($rv -eq 0)
{
"$($_.Name) uninstalled successfully"
} # Changed this round bracket to a squigly one to prperly close the scriptblock for "if"
else
{
"There was an error ($rv) uninstalling $($_.Name)"
}
}
Related
The majority of this code was pulled from a blog online, but I think it's exactly the way I need to be tackling this. I want to get the top 4 machines from an OU based on uptime, and run a script that lives on each of the top 4 machines. I know that the problem involves the Array losing access to the Get-ADComputer properties, but I'm unsure of how to pass these new properties back to their original objects. This works as expected until it gets to the foreach loop at the end.
$scriptBlock={
$wmi = Get-WmiObject -Class Win32_OperatingSystem
($wmi.ConvertToDateTime($wmi.LocalDateTime) – $wmi.ConvertToDateTime($wmi.LastBootUpTime)).TotalHours
}
$UpTime = #()
Get-ADComputer -Filter 'ObjectClass -eq "Computer"' -SearchBase "OU=***,OU=***,OU=***,DC=***,DC=***" -SearchScope Subtree `
| ForEach-Object { $Uptime += `
(New-Object psobject -Property #{
"ComputerName" = $_.DNSHostName
"UpTimeHours" = (Invoke-Command -ComputerName $_.DNSHostName -ScriptBlock $scriptBlock)
}
)
}
$UpTime | Where-Object {$_.UpTimeHours -ne ""} | sort-object -property #{Expression="UpTimeHours";Descending=$true} | `
Select-Object -Property ComputerName,#{Name="UpTimeHours"; Expression = {$_.UpTimeHours.ToString("#.##")}} | Select-Object -First 4 |`
Format-Table -AutoSize -OutVariable $Top4.ToString()
foreach ($Server in $Top4.ComputerName) {
Invoke-Command -ComputerName $Server -ScriptBlock {HOSTNAME.EXE}
}
I'm not married to Invoke-Command in the last foreach but am having the same issues when I try to use psexec. Also, I'm running hostname.exe as a check to make sure I'm looping through the correct machines before I point it at my script.
Here's a streamlined version of your code, which heeds the comments on the question:
# Get all computers of interest.
$computers = Get-ADComputer -Filter 'ObjectClass -eq "Computer"' -SearchBase "OU=***,OU=***,OU=***,DC=***,DC=***" -SearchScope Subtree
# Get the computers' up-times in hours.
# The result will be [double] instances, but they're also decorated
# with .PSComputerName properties to identify the computer of origin.
$upTimes = Invoke-Command -ComputerName $computers.ConputerName {
((Get-Date) - (Get-CimInstance -Class Win32_OperatingSystem).LastBootUpTime).TotalHours
}
# Get the top 4 computers by up-time.
$top4 = $upTimes | Sort-Object -Descending | Select-Object -First 4
# Invoke a command on all these 4 computers in parallel.
Invoke-Command -ComputerName $top4.PSComputerName -ScriptBlock { HOSTNAME.EXE }
Can someone help me with this powershell script?
I can't get it to work.
$drives = Get-WmiObject -class win32_logicaldisk |
Where-Object { $_.DriveType -eq 3 }
$drives | ForEach-Object { Enable-ComputerRestore $_.Name }
Moreover the following works like expected!
$drives = Get-WmiObject -class win32_logicaldisk |
Where-Object { $_.DriveType -eq 3 }
$drives | ForEach-Object { Write-Host $_.Name }
Thanks in advance.
Enable-ComputerRestore seems to have two unusual requirements:
The (positionally implied) -Drive argument(s) must end in \ (backslash), e.g, C:\
In order to target a drive other than the system drive, the system drive must also be specified, alongside the non-system drive or System Restore must already be turned on for the system drive.
Since your code enumerates all local disks (.DriveType -eq 3), the system drive is by definition among them, so the simplest solution is to pass all local drives at once to Enable-ComputerRestore, as an array:
$driveSpecs =
Get-CimInstance -Class Win32_LogicalDisk |
Where-Object { $_.DriveType -eq 3 } |
ForEach-Object { $_.Name + '\' }
Enable-ComputerRestore $driveSpecs
As an aside:
I've replaced Get-WmiObject with Get-CimInstance, because the CIM cmdlets superseded the WMI cmdlets in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell Core (version 6 and above), where all future effort will go, doesn't even have them anymore. For more information, see this answer.
A more concise and more efficient way to filter the Get-CimInstance output is to use a -Filter argument in lieu of a separate Where-Object call:
Get-CimInstance -Class Win32_LogicalDisk -Filter 'DriveType = 3'
Found the solution by my self. Posting for others.
$OSDriveLetter = (Get-ComputerInfo | Select-Object -Property "OsSystemDrive").OsSystemDrive
Get-CimInstance -class win32_logicaldisk |
Where-Object { $_.DriveType -eq 3 } |
ForEach-Object { Enable-ComputerRestore "$($_.Name + '\')", "$($OSDriveLetter + '\')" }
I'm struggling to have two conditions met, somehow it seems only one of them works. I'm trying to do :
If a user is connected AND NOT at the lock screen, ask for permission. The commands themselves have been verified and work individually but I must be missing something. Here is what i have:
if (Get-WmiObject –ComputerName $poste –Class Win32_ComputerSystem | Select-Object -expandproperty UserName -and -not (get-process -ComputerName $poste -name logonui)) {
"ask for permission"
}
Right now it just doesn't go in this code, it skips to the lower part where something else is happening.
What is wrong with my syntax ?
I can work around it and make it work this old fashioned way from my CMD days:
Clear-Variable -name statut_user
$statut_user -eq 0
if (Get-WmiObject –ComputerName $poste –Class Win32_ComputerSystem | Select-Object -expandproperty UserName) {$statut_user +=1}
if (-not (get-process -ComputerName $poste -name logonui)) {$statut_user += 1}
if ($statut_user -eq 2) {
"ask for permission"
}
It works, but not as clean as a proper one liner with the two conditions. Thank you for your help!
ANSWER EDIT: Thanks to vonPryz's answer below i ended up using :
$utilisateur = Get-WmiObject –ComputerName $poste –Class Win32_ComputerSystem | Select-Object -expandproperty UserName
$ecran_verr = get-process -ComputerName $poste -name logonui
if( -not ($ecran_verr) -and ($utilisateur)) {
"ask for permission"
}
Aim for clarity, not small codebase size. Instead of making WMI calls in the if statement and piping the results, consider something more readable. Like so,
$cs = gwmi -computername $p Win32_ComputerSystem
$uname = $cs | select-object -expandproperty UserName
$logonui = get-process -ComputerName $p -name logonui
if( -not ($logonui) -and ($uname )) {
# Stuff
}
This approach makes it easy to check that the WMI objects contain sensible values, whatever those may be. Then it should be easier to write a concise conditional statement.
While breaking an expression down into multiple steps is always a good idea for debugging, as demonstrated in vonPryz's helpful answer, sometimes you do want the concision of a single expression without auxiliary variables.
What is wrong with my syntax?
You're missing (...) around the Get-WmiObject ... | Select-Object ... pipeline.
To use a command or pipeline as part of a larger expression, you must always enclose it in (...) A command in PowerShell is a call to an executable - be it a cmdlet, function, alias, or external program.
A simple example:
# !! BROKEN: tokens `-eq 'jdoe'` are interpreted as *arguments for Select-Object*
# !! rather than as operator -eq and RHS 'jdoe'
if (Get-WmiObject Win32_ComputerSystem | Select-Object -Expand UserName -ne 'jdoe') {
'not jdoe'
}
# OK: (...) around the pipeline properly embeds it in the overall expression:
if ((Get-WmiObject Win32_ComputerSystem | Select-Object -Expand UserName) -ne 'jdoe') {
'not jdoe'
}
Here's a fixed version of your original command that fixes that improves other aspects too:
if (
(Get-WmiObject –ComputerName $poste –Class Win32_ComputerSystem).UserName `
-and -not `
(Get-Process -ErrorAction SilentlyContinue -ComputerName $poste -name logonui)
) {
"ask for permission"
}
Given that your Get-WmiObject call only ever outputs 1 object, you can access the .UserName property directly, which is also more efficient than piping to Select-Object -ExpandProperty.
Get-Process outputs a non-terminating error if a process by the given name cannot be found, so -ErrorAction SilentlyContinue suppresses that.
Note the use of ` as a line-continuation character, which allows spreading the conditional across multiple lines, making it much more readable.
Note that the ` must be at the very end of the line.
I have a PowerShell that would uninstall a program from remote computers but it takes a long time since it goes through all the computers in the list. I just need help from you to modify it so it first checks if the program exists on the remote computer or not and then uninstalls it:
$comps = gc "C:\Computers.txt"
$appname = gc "C:\appname.txt"
foreach($comp in $comps){
foreach ($appname in $appname){
$prod=gwmi -computer $comp win32_product | ?{$_.name -eq "$appname"}
$prod.uninstall()
}
}
Try using foreach –parallel instead of just foreach. Official docs here.
workflow uninstallstuff {
sequence {
$comps = gc "C:\Computers.txt"
$appname = gc "C:\appname.txt"
foreach -parallel ($comp in $comps){
foreach ($appname in $appname){
$prod=gwmi -computer $comp win32_product | ?{$_.name -eq "$appname"}
$prod.uninstall()
}
}
}
}
That should run each computer in parallel but each app for that computer will be uninstalled sequentially.
edit: rewrote as a workflow. I haven't tested it yet.
Got it! thanks
$comps= gc "C:\Computers.txt"
$appname = gc "C:\appname.txt"
foreach($comp in $comps){
$program = gwmi -computer $comp Win32_Product | sort-object Name | select Name | where { $_.Name -match “$appname”}
if($program -eq $null)
{
Write-host "Does Not"
}
else
{
$prod=gwmi -computer $comp win32_product | ?{$_.name -eq "$appname"}
$prod.uninstall()
}
}
I'm trying to list all of the shares on a computer that aren't hidden. But I just can't get the where-object clause to work. Any idea how I would filter out all of the share names that have a dollar sign in them? Right now this filters nothing out.
$ComputerName = "server"
$Shares = get-wmiobject -class Win32_share -ComputerName $ComputerName
$Shares | Where-Object -FilterScript { $_.Name.tostring() -notcontains "\`$" }
I know this is simple, but I just can't figure it out.
edit:
Here's my resulting script if anyone wants to copy (names changed to protect the innocent):
$ComputerNames = "server1","server2","server3","server4"
$Shares = invoke-command $ComputerNames { get-wmiobject -class Win32_share } -ErrorAction "SilentlyContinue"
$Shares | Where-Object -FilterScript { $_.Name.tostring() -notlike '*$*' }
I don't know how it compares to containment operators for speed, but I would use a match operator to do this:
$Shares | Where-Object -FilterScript { $_.Name.tostring() -notlike '*$*' }
Like #EBGreen said, should do:
$Shares | Where-Object -FilterScript { $_.Name.tostring() -notlike '*$*' }
Reason being is that -notcontains is used for array searching.