Which is the most comprehensive way to check applied KBs to a Windows machine? - powershell

I am using Powershell to run a status of a list of KBs and see if they are applied or not.
I have found a few ways and I have seen inconsistencies with the numbers they are reporting. Which is right?
You can check SYSTEMINFO and get a list of hotfixes. You can also use the Get-Hotfix cmdlet, which is an alias for gwmi Win32_QuickFixEngineering or you can use wmic qfe list (WMI-CLI QuickFixEngineering List).
So, why am I getting different numbers when I do a quick count?
i.e. (Get-HotFix).Count and (wmic qfe list).Count
Using those two returns 153 and 310, respectively.
What gives? Why does it return different values? Are all of the applied KBs not listed in the Get-Hotfix cmdlet?
Before anyone asks, yes, I have restarted the machine and I haven't applied any since it was restarted and updated. That is Day 1 stuff...

WMIC has obscure blank lines which might be muddying the waters a bit. Here's simple, not very good, parser for wmic qfe (Windows 10 so who knows if it'll transpose).
The hope is that you can compare the lists.
$qfe = wmic qfe list brief | Select-Object -Skip 1 | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object {
[PSCustomObject]#{
Description = $_.Substring(0, 17).Trim()
HotFixId = $_.Substring(30, 10).Trim()
}
}
Compare-Object (Get-HotFix) $qfe -Property HotFixID -IncludeEqual
So is that enough? No, not really. QFE is great but indicative only. I'm trying to remember the circumstance that invalidates it. I'll come back to this.

Related

Get serialnumber from asset list

Started in recent weeks in a Junior infrastructure role, and begun playing around with powershell to help save some time here and there.
I am trying to do the following:
1- I'm port a CSV file with a single column names asset
2- Perform a "ForEach" check on each line to find the device's serial number
3- Output results to a CSV with two column "asset" and "serialnumber"
I have dabbled in a few areas, and am currently sitting at something like this:
$file1 = Import-Csv -path "c:\temp\assets.csv" | ForEach-Object {
$asset = $_.asset
}
wmic /node:$asset bios get serialnumber
Export-Csv -Path "c:\temp\assetandserial.csv" -NoTypeInformation
As you may or may not see, I tried to set the column labelled "asset" as the variable, however, not sure if I placed it correctly.
I have tried a few other things, but honestly it's all new to me, so I haven't the foggiest idea where to go from here.
wmic is deprecated, and, for rich type support (OO processing), using PowerShell-native cmdlets is preferable in general.
wmic's immediate PowerShell counterpart is Get-WmiObject, which, however, is equally deprecated, in favor of Get-CimInstance.
Important: The command below uses Get-CimInstance, but note that the CIM cmdlets use a different remoting protocol than the obsolete WMI cmdlets. In short: To use the CIM cmdlets with remote computers, those computers must be set up in the same way that PowerShell remoting requires - see this answer for details.
Get-CimInstance Win32_BIOS -ComputerName (Import-Csv c:\temp\assets.csv).asset |
Select-Object #{ n='Asset'; e='PSComputerName' }, SerialNumber |
Sort-Object Asset |
Export-Csv c:\temp\assetandserial.csv -NoTypeInformation
Note the use of member-access enumeration to extract all .asset values directly from the collection of objects returned from Import-Csv.
All computer (asset) names are passed at once to Get-CimInstance, which queries them in parallel. Since the ordering of the responses from the targeted remote machines isn't guaranteed, Sort-Object is used to sort the results.
A calculated property is used with Select-Object to rename the automatically added .PSComputerName property to Asset.

Where-Object, Select-Object and ForEach-object - Differences and Usage

Where-Object, Select-Object and ForEach-Object
I am a PowerShell beginner. I don't understand too much. Can someone give examples to illustrate the differences and usage scenarios between them?
If you are at all familiar with either LINQ or SQL then it should be much easier to understand because it uses the same concepts for the same words with a slight tweak.
Where-Object
is used for filtering out objects from the pipeline and is similar to how SQL filters rows. Here, objects are compared against a condition, or optionally a ScriptBlock, to determine whether it should be passed on to the next cmdlet in the pipeline. To demonstrate:
# Approved Verbs
Get-Verb | Measure-Object # count of 98
Get-Verb | Where-Object Verb -Like G* | Measure-Object # 3
# Integers 1 to 100
1..100 | Measure-Object # count of 100
1..100 | Where-Object {$_ -LT 50} | Measure-Object # count of 49
This syntax is usually the most readable when not using a ScriptBlock, but is necessary if you want to refer to the object itself (not a property) or for more complicated boolean results. Note: many resources will recommend (as #Iftimie Tudor mentions) trying to filter sooner (more left) in the pipeline for performance benefits.
Select-Object
is used for filtering properties of an object and is similar to how SQL filters columns. Importantly, it transforms the pipeline object into a new PSCustomObject that only has the requested properties with the object's values copied. To demonstrate:
Get-Process
Get-Process | Select-Object Name,CPU
Note, though, that this is only the standard usage. Explore its parameter sets using Get-Help Select-Object where it has similar row-like filtering capabilities like only getting the first n objects from the pipeline (aka, Get-Process | Select-Object -First 3) that continue onto the next cmdlet.
ForEach-Object
is like your foreach loops in other languages, with its own important flavour. In fact, PowerShell also has a foreach loop of its own! These may be easily confused but are operationally quite different. The main visual difference is that the foreach loop cannot be used in a pipeline, but ForEach-Object can. The latter, ForEach-Object, is a cmdlet (foreach is not) and can be used for transforming the current pipeline or for running a segment of code against the pipeline. It is really the most flexible cmdlet there is.
The best way to think about it is that it is the body of a loop, where the current element, $_, is coming from the pipeline and any output is passed onto the next cmdlet. To demonstrate:
# Transform
Get-Verb | ForEach-Object {"$($_.Verb) comes from the group $($_.Group)"}
# Retrieve Property
Get-Verb | ForEach-Object Verb
# Call Method
Get-Verb | ForEach-Object GetType
# Run Code
1..100 | ForEach-Object {
$increment = $_ + 1
$multiplied = $increment * 3
Write-Output $multiplied
}
Edit (Feb, 2023): thanks to #IkemKrueger for a missing }.
You have two things in there: filtering and iterating through a collection.
Filtering:
principle: Always use filtering left as much as possible. These two commands do the same thing, but the second one won't transmit a huge chunk of data through the pipe (or network):
Get-Process | where-Object {$_.Name -like 'chrome'} | Export-Csv
'c:\temp\processes.csv'
Get-Process -Name chrome | Export-Csv c:\temp\processes.csv
This is great when working with huge lists of computers or big files.
Many commandlets have their own filtering capabilities. Run get Get-Help get-process -full to see what they offer before piping.
iterating through collections:
Here you have 3 possibilities:
batch cmdlets is commandlet built in capability of passing a collection to another commandlet:
Get-Service -name BITS,Spooler,W32Time | Set-Service -startuptype
Automatic
WMI methods - WMI uses it's own way of doing the first one (different syntax)
gwmi win32_networkadapterconfiguration -filter "description like
'%intel%'" | EnableDHCP()
enumerating objects - iterating through the list:
Get-WmiObject Win32_Service -filter "name = 'BITS'" | ForEach-Object
-process { $_.change($null,$null,$null,$null,$null,$null,$null,"P#ssw0rd") }
Credits:
I found explanations that cleared the mess in my head around all these things in a book called : Learn Powershell in a month of lunches (chapters 9 and 13 in this case)

Pulling duplicate items from print server

I have a script that has two comboboxes. The first selects a location, the second populates depending on the first combobox selecteditem and displays available printer names for that location.
What I see happen more often than not is the list has duplicates of all of its items. Code below;
$Hospital = Get-Printer -ComputerName \\PrintServer | where {$_.Name -like “*Name*”}
$ComboBox_Location.Add_SelectedIndexChanged{
switch ($ComboBox_Location.SelectedItem){
"Hospital"{
$ComboBox_Printer.Items.Clear();
foreach($Name in $Hospital){
$ComboBox_Printer.Items.Add("$($Name.name)");
}
}
I can provide more code if it is needed. I believe the problem is in this particular code, but you can tell me otherwise. I also took out the ("$($Name.name)") and replaced it with "$Name.name", the following are results of the test;
MSFT_Printer(Name="PrinterName")
MSFT_3DPrinter(Name="PrinterName")
They were the same name. It was the duplicate. Is something needing a change in my print server?
Thanks in advance!
I found what was wrong. The devicetype was not defined. It needed to be;
$Hospital = Get-Printer -ComputerName \\PrintServer | where {($_.Name -like “*Name*”) -and ($_.DeviceType -eq "Print")}
This excluded any other types of devices (like the 3DPrinter) from populating in the list.

Get current version number of specified program

I started with this, on a recommendation from a friend
Get-WmiObject win32_product | ft name, version
But then I found this, which gives me pause.
A little research led me to this
wmic product where "Name='Revit 2018'" get name,version
Which works as far as the data gathered. And yes, I am looking for a different program in this example. in any case, once I had good info using WMIC I tried to get the data into a variable so I could get just the version number, but the data formatting is something I have never seen before. I was hoping for a simple solution, like
$object = wmic product where "Name='Revit 2018'" get name,version
$object.version
But only the result is an array with 6 items, and only one seems to be the actual data line, and that's a single line, not two properties. And, I really wonder if an old command line utility is the right answer here. If it really is the best way to do this, is there a trick to converting the raw data to something more, PowerShelly? And if it's not the best way to get this info, what is? Is that scary link real, or is Get-WmiObject win32_product actually safe? And if so, is there a way to filter on a specific name, to speed things up? And indeed, Get-WmiObject doesn't work as I was expecting, as
$object = Get-WmiObject win32_product | ft name, version
foreach ($item in $object) {
Write-Host "$($item.version)"
}
Doesn't work as expected at all.
EDIT: This seems to be working as expected, which is progress.
$version = (Get-WmiObject win32_product -filter:"Name = 'Revit 2018'" | Select-Object -property:*).version
Write-Host "$version!"
I guess the question is really, is this a safe and consistent approach, or is there a better one?
Why not use the registry?
Set-Location HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall
$app = Get-ChildItem | Where-Object { $_.GetValue("DisplayName") -match 'YourSoftware' }
$app.GetValue("DisplayVersion")
Or
Set-Location HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall
$apps = Get-ChildItem
foreach ($app in $apps) {
$app.GetValue("DisplayName","DisplayVersion")
}
Note: You'll also need to check the SysWow64 registry location as well
HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\
Note: Not all items will have a display version in which case you always have the option of looking in the installation directory for the executable itself, which should have a version on it.

How to count objects in PowerShell?

As I'm reading in the PowerShell user guide, one of the core PowerShell concepts is that commands accept and return objects instead of text. So for example, running get-alias returns me a number of System.Management.Automation.AliasInfo objects:
PS Z:\> get-alias
CommandType Name Definition
----------- ---- ----------
Alias % ForEach-Object
Alias ? Where-Object
Alias ac Add-Content
Alias asnp Add-PSSnapIn
Alias cat Get-Content
Alias cd Set-Location
Alias chdir Set-Location
...
Now, how do I get the count of these objects?
This will get you count:
get-alias | measure
You can work with the result as with object:
$m = get-alias | measure
$m.Count
And if you would like to have aliases in some variable also, you can use Tee-Object:
$m = get-alias | tee -Variable aliases | measure
$m.Count
$aliases
Some more info on Measure-Object cmdlet is on Technet.
Do not confuse it with Measure-Command cmdlet which is for time measuring. (again on Technet)
As short as #jumbo's answer is :-) you can do it even more tersely.
This just returns the Count property of the array returned by the antecedent sub-expression:
#(Get-Alias).Count
A couple points to note:
You can put an arbitrarily complex expression in place of Get-Alias, for example:
#(Get-Process | ? { $_.ProcessName -eq "svchost" }).Count
The initial at-sign (#) is necessary for a robust solution. As long as the answer is two or greater you will get an equivalent answer with or without the #, but when the answer is zero or one you will get no output unless you have the # sign! (It forces the Count property to exist by forcing the output to be an array.)
2012.01.30 Update
The above is true for PowerShell V2. One of the new features of PowerShell V3 is that you do have a Count property even for singletons, so the at-sign becomes unimportant for this scenario.
Just use parenthesis and 'count'. This applies to Powershell v3
(get-alias).count
#($output).Count does not always produce correct results.
I used the ($output | Measure).Count method.
I found this with VMware Get-VmQuestion cmdlet:
$output = Get-VmQuestion -VM vm1
#($output).Count
The answer it gave is one, whereas
$output
produced no output (the correct answer was 0 as produced with the Measure method).
This only seemed to be the case with 0 and 1. Anything above 1 was correct with limited testing.
in my exchange the cmd-let you presented did not work, the answer was null, so I had to make a little correction and worked fine for me:
#(get-transportservice | get-messagetrackinglog -Resultsize unlimited -Start "MM/DD/AAAA HH:MM" -End "MM/DD/AAAA HH:MM" -recipients "user#domain.com" | where {$_.Event
ID -eq "DELIVER"}).count
Get-Alias|ForEach-Object {$myCount++};$myCount
158
Please try this to get total count of objects.
(Get-Alias).Count